package com.afc.struts.form;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;

import com.afc.struts.annotations.PossibleValues;
import com.afc.struts.asserts.ActionException;
import com.afc.struts.asserts.FormatException;
import com.afc.struts.utils.Constants;
import com.afc.struts.utils.DateUtils;
import com.afc.struts.utils.Report;
import com.afc.struts.utils.StringUtils;

public interface ActionFormFactory
{
  public <FormType extends ActionForm> FormType getActionForm(Class<FormType> aClass, HttpServletRequest aRequest) throws ActionException;

  public class DefaultActionFormFactory implements ActionFormFactory
  {
    public DefaultActionFormFactory(ServletConfig aServletConfig)
    {
    }

    @Override
    public <FormType extends ActionForm> FormType getActionForm(Class<FormType> aClass, HttpServletRequest aRequest) throws ActionException
    {
      try
      {
        FormType form = aClass.newInstance();
        Enumeration<String> parameterNames = aRequest.getParameterNames();
        while (parameterNames.hasMoreElements())
        {
          String parameterName = parameterNames.nextElement();
          String parameter = aRequest.getParameter(parameterName);
          mutateParameter(form, parameterName, parameter, aRequest);
        }
        return form;
      }
      catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException
          | InvocationTargetException anException)
      {
        throw new ActionException("Unable to get ActionForm " + aClass + " from request", anException);
      }
    }

    protected void mutateParameter(ActionForm aForm, String aParameterName, String aParameter, HttpServletRequest aRequest) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ActionException
    {
      Method getter = getGetter(aForm, aParameterName);
      Object parameterValue = getParameterValue(aForm, getter, aParameterName, aParameter, aRequest);
      String setterName = "set" + StringUtils.toUpperCaseFirstChar(aParameterName);
      Method setter = aForm.getClass().getMethod(setterName, new Class[]
        {
          getter.getReturnType()
        });
      setter.invoke(aForm, parameterValue);
    }

    protected Method getGetter(ActionForm aForm, String aParameterName) throws NoSuchMethodException, SecurityException
    {
      String getterName = "get" + StringUtils.toUpperCaseFirstChar(aParameterName);
      return aForm.getClass().getMethod(getterName, new Class[]
        {});
    }

    protected Object getParameterValue(ActionForm aForm, Method aGetter, String aParameterName, String aParameter, HttpServletRequest aRequest) throws ActionException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
      try
      {
        PossibleValues possibleValuesAnnotation = aGetter.getAnnotation(PossibleValues.class);
        if (possibleValuesAnnotation != null)
        {
          Method possibleValuesMethod = aForm.getClass().getMethod(possibleValuesAnnotation.value(), new Class[]
            {});
          List possibleValues = (List)possibleValuesMethod.invoke(aForm);
          return possibleValues.get(Integer.valueOf(aParameter));
        }
        return convertParameter(aGetter.getReturnType(), aParameter);
      }
      catch (FormatException anException)
      {
        Report.error("Error Format", "Unable to mutate form " + aParameterName + " parameter with value " + aParameter, anException, aRequest);
        return Constants.getDefaultValue(aGetter.getReturnType());
      }
    }

    @SuppressWarnings("unchecked")
    protected Object convertParameter(Class aType, String aParameter) throws ActionException, FormatException
    {
      try
      {
        if (String.class.isAssignableFrom(aType))
        {
          return aParameter;
        }
        else if (aType.isEnum())
        {
          return StringUtils.isFeed(aParameter) ? Enum.valueOf(aType, aParameter) : Constants.getDefaultValue(aType);
        }
        else if (Integer.TYPE.isAssignableFrom(aType) || Integer.class.isAssignableFrom(aType))
        {
          return StringUtils.isFeed(aParameter) ? Integer.valueOf(aParameter) : Constants.getDefaultValue(aType);
        }
        else if (Short.TYPE.isAssignableFrom(aType) || Short.class.isAssignableFrom(aType))
        {
          return StringUtils.isFeed(aParameter) ? Short.valueOf(aParameter) : Constants.getDefaultValue(aType);
        }
        else if (Long.TYPE.isAssignableFrom(aType) || Long.class.isAssignableFrom(aType))
        {
          return StringUtils.isFeed(aParameter) ? Long.valueOf(aParameter) : Constants.getDefaultValue(aType);
        }
        else if (Double.TYPE.isAssignableFrom(aType) || Double.class.isAssignableFrom(aType))
        {
          return StringUtils.isFeed(aParameter) ? Double.valueOf(aParameter) : Constants.getDefaultValue(aType);
        }
        else if (Float.TYPE.isAssignableFrom(aType) || Float.class.isAssignableFrom(aType))
        {
          return StringUtils.isFeed(aParameter) ? Float.valueOf(aParameter) : Constants.getDefaultValue(aType);
        }
        else if (Character.TYPE.isAssignableFrom(aType) || Character.class.isAssignableFrom(aType))
        {
          if (aParameter.length() > 1)
          {
            throw new FormatException("Invalid character length for " + aParameter);
          }
          return StringUtils.isFeed(aParameter) ? new Character(aParameter.charAt(0)) : Constants.getDefaultValue(aType);
        }
        else if (Boolean.TYPE.isAssignableFrom(aType) || Boolean.class.isAssignableFrom(aType))
        {
          return StringUtils.isFeed(aParameter) ? Boolean.valueOf(aParameter) : Constants.getDefaultValue(aType);
        }
        else if (Date.class.isAssignableFrom(aType))
        {
          return StringUtils.isFeed(aParameter) ? DateUtils.parse(aParameter) : Constants.getDefaultValue(aType);
        }
      }
      catch (FormatException anException)
      {
        throw anException;
      }
      catch (Exception anException)
      {
        throw new FormatException("Unable to convert " + aParameter + " into to type " + aType, anException);
      }
      throw new ActionException("Unsupported form parameter type " + aType);
    }
  }
}
